using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace GameLibrary
{
    /// <summary>
    /// Used to convert user input to a platform independent language
    /// </summary>
    /// <remarks></remarks>
    public class InputManager : IInputManager
    {
        /// <summary>
        /// Indicates which gamepads are active
        /// </summary>
        private bool[] _activeControllers = new bool[4];

        /// <summary>
        /// Used to hold the specfic key bindings for each player (PC)
        /// </summary>
        private readonly PcPlayerKeys[] _pcPlayerKeys = new PcPlayerKeys[4];

        /// <summary>
        /// Prevents flickering between states
        /// </summary>
        private bool _pausePressed;

        /// <summary>
        /// Keeps track of which player has paused (Xbox)
        /// </summary>
        private Dictionary<PlayerIndex, bool> playerPaused;

        /// <summary>
        /// Initializes a new instance of the <see cref="InputManager"/> class.
        /// </summary>
        /// <remarks></remarks>
        public InputManager()
        {
#if XBOX360
            InitPlayersXbox();
#else
            InitPlayersPC();
#endif
        }

        /// <summary>
        /// Checks the input from a specfic player depending on platform.
        /// </summary>
        /// <param name="playerIndex">Index of the player.</param>
        /// <returns>A string describing the action</returns>
        /// <remarks></remarks>
        public Constant.Action CheckInput(PlayerIndex playerIndex)
        {
#if XBOX360
            return CheckInputXbox(playerIndex);
#else
            return CheckInputPC(playerIndex);
#endif
        }

        /// <summary>
        /// Checks the user input depending on platform.
        /// </summary>
        /// <returns>A string describing the action</returns>
        /// <remarks></remarks>
        public Constant.Action CheckInput()
        {
#if XBOX360
            return CheckInputXbox();
#else
            return CheckInputPC();
#endif
        }

        /// <summary>
        /// Checks if a player has been disconnected(XBOX).
        /// </summary>
        /// <returns>true if a player has been disconnected, false otherwise</returns>
        /// <remarks></remarks>
        public bool CheckDisconnect()
        {
            bool disconnectDetected = false;
            int i = 0;
            for (PlayerIndex pi = PlayerIndex.One; pi <= PlayerIndex.Four; pi++, i++)
            {
                GamePadState state = GamePad.GetState(pi);
                //Check if the controller is active but not connected
                if (!state.IsConnected && _activeControllers[i])
                {
                    disconnectDetected = true;
                }
            }
            return disconnectDetected;
        }

        /// <summary>
        /// Gets the active players.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        public bool[] GetActivePlayers()
        {
            return _activeControllers;
        }

        /// <summary>
        /// Inits the players key settings (PC).
        /// </summary>
        /// <remarks></remarks>
        private void InitPlayersPC()
        {
            _pcPlayerKeys[0] = new PcPlayerKeys
            {
                Up = InputConstant.PcPlayerOneUp,
                Down = InputConstant.PcPlayerOneDown,
                Right = InputConstant.PcPlayerOneRight,
                Left = InputConstant.PcPlayerOneLeft,
                Attack = InputConstant.PcPlayerOneAttack
            };

            _pcPlayerKeys[1] = new PcPlayerKeys
            {
                Up = InputConstant.PcPlayerTwoUp,
                Down = InputConstant.PcPlayerTwoDown,
                Right = InputConstant.PcPlayerTwoRight,
                Left = InputConstant.PcPlayerTwoLeft,
                Attack = InputConstant.PcPlayerTwoAttack
            };

            _pcPlayerKeys[2] = new PcPlayerKeys
            {
                Up = InputConstant.PcPlayerThreeUp,
                Down = InputConstant.PcPlayerThreeDown,
                Right = InputConstant.PcPlayerThreeRight,
                Left = InputConstant.PcPlayerThreeLeft,
                Attack = InputConstant.PcPlayerThreeAttack
            };

            _pcPlayerKeys[3] = new PcPlayerKeys
            {
                Up = InputConstant.PcPlayerFourUp,
                Down = InputConstant.PcPlayerFourDown,
                Right = InputConstant.PcPlayerFourRight,
                Left = InputConstant.PcPlayerFourLeft,
                Attack = InputConstant.PcPlayerFourAttack
            };
        }

        /// <summary>
        /// Inits the players (XBOX).
        /// </summary>
        /// <remarks></remarks>
        private void InitPlayersXbox()
        {
            playerPaused = new Dictionary<PlayerIndex, bool>(4);
            int i = 0;
            for (PlayerIndex pi = PlayerIndex.One; pi <= PlayerIndex.Four; pi++, i++)
            {
                GamePadState state = GamePad.GetState(pi);
                playerPaused[pi] = false;
                if (state.IsConnected)
                {
                    _activeControllers[i] = true;
                }
            }
        }

        /// <summary>
        /// Checks the input (PC) from a specific player.
        /// </summary>
        /// <param name="playerIndex">Index of the player.</param>
        /// <returns>A string describing the action</returns>
        /// <remarks></remarks>
        private Constant.Action CheckInputPC(PlayerIndex playerIndex)
        {
            //Get the keyboard state
            KeyboardState state = Keyboard.GetState();

            //Get the key bindings for the specific player
            PcPlayerKeys keys = _pcPlayerKeys[(int)playerIndex];

            //Check user input
            if (state.IsKeyDown(keys.Attack))
                return Constant.Action.Attack;
            if (state.IsKeyDown(keys.Up))
            {
                return state.IsKeyDown(keys.Right) ? Constant.Action.MoveUpRight : (state.IsKeyDown(keys.Left) ? Constant.Action.MoveUpLeft : Constant.Action.MoveUp);
            }
            if (state.IsKeyDown(keys.Down))
            {
                return state.IsKeyDown(keys.Right) ? Constant.Action.MoveDownRight : (state.IsKeyDown(keys.Left) ? Constant.Action.MoveDownLeft : Constant.Action.MoveDown);
            }
            if (state.IsKeyDown(keys.Left))
                return Constant.Action.MoveLeft;
            if (state.IsKeyDown(keys.Right))
                return Constant.Action.MoveRight;

            //If no action found, return empty string
            return Constant.Action.DoNothing;
        }

        /// <summary>
        /// Checks the input (XBOX) from a specific player.
        /// </summary>
        /// <param name="playerIndex">Index of the player.</param>
        /// <returns>A string describing the action</returns>
        /// <remarks></remarks>
        private Constant.Action CheckInputXbox(PlayerIndex playerIndex)
        {
            //Get the gamepad state for the current player
            GamePadState state = GamePad.GetState(playerIndex);

            //Check user input
            if (state.Triggers.Right > 0)
                return Constant.Action.Attack;
            if (state.ThumbSticks.Left.Y > 0)
            {
                return state.ThumbSticks.Left.X > 0 ? Constant.Action.MoveUpRight : (state.ThumbSticks.Left.X < 0 ? Constant.Action.MoveUpLeft : Constant.Action.MoveUp);
            }
            if (state.ThumbSticks.Left.Y < 0)
            {
                return state.ThumbSticks.Left.X > 0 ? Constant.Action.MoveDownRight : (state.ThumbSticks.Left.X < 0 ? Constant.Action.MoveDownLeft : Constant.Action.MoveDown);
            }
            if (state.ThumbSticks.Left.X > 0)
                return Constant.Action.MoveRight;
            if (state.ThumbSticks.Left.X < 0)
                return Constant.Action.MoveLeft;

            //If no action found, return empty string
            return Constant.Action.DoNothing;
        }

        /// <summary>
        /// Checks the input from all users (PC).
        /// </summary>
        /// <returns>A string describing the action</returns>
        /// <remarks></remarks>
        private Constant.Action CheckInputPC()
        {
            //Get the keyboard state
            KeyboardState state = Keyboard.GetState();

            //Check if pause key was released
            if (_pausePressed && state.IsKeyUp(InputConstant.PcPause))
                _pausePressed = false;

            //Check user input
            if (state.IsKeyDown(InputConstant.PcPause) && !_pausePressed)
            {
                _pausePressed = true;
                return Constant.Action.Pause;
            }

            if (state.IsKeyDown(Keys.Space))
                return Constant.Action.Start;

            //If no action found, return empty string
            return Constant.Action.DoNothing;
        }

        /// <summary>
        /// Checks the from all users (XBOX).
        /// </summary>
        /// <returns>A string describing the action</returns>
        /// <remarks></remarks>
        private Constant.Action CheckInputXbox()
        {
            for (PlayerIndex pi = PlayerIndex.One; pi <= PlayerIndex.Four; pi++)
            {
                GamePadState state = GamePad.GetState(pi);

                //Check user input
                if (state.Buttons.A == ButtonState.Pressed)
                    return Constant.Action.Start;

                if (state.Buttons.Start == ButtonState.Pressed && !playerPaused[pi])
                {
                    playerPaused[pi] = true;
                    return GameLibrary.Constant.Action.Pause;
                }

                //Check if pause key was released
                if (playerPaused[pi] && state.Buttons.Start == ButtonState.Released)
                    playerPaused[pi] = false;
            }
            //If no action found, return empty string
            return Constant.Action.DoNothing;
        }

        /// <summary>
        /// Struct containing player actions mapped to keys
        /// </summary>
        /// <remarks></remarks>
        private struct PcPlayerKeys
        {
            internal Keys Up;
            internal Keys Down;
            internal Keys Right;
            internal Keys Left;
            internal Keys Attack;
        }

        /// <summary>
        /// Static class for PC players key bindings
        /// </summary>
        /// <remarks></remarks>
        private static class InputConstant
        {
            internal const Keys PcPlayerOneUp = Keys.W;
            internal const Keys PcPlayerOneDown = Keys.S;
            internal const Keys PcPlayerOneRight = Keys.D;
            internal const Keys PcPlayerOneLeft = Keys.A;
            internal const Keys PcPlayerOneAttack = Keys.Space;

            internal const Keys PcPlayerTwoUp = Keys.Up;
            internal const Keys PcPlayerTwoDown = Keys.Down;
            internal const Keys PcPlayerTwoRight = Keys.Right;
            internal const Keys PcPlayerTwoLeft = Keys.Left;
            internal const Keys PcPlayerTwoAttack = Keys.RightControl;

            internal const Keys PcPlayerThreeUp = Keys.I;
            internal const Keys PcPlayerThreeDown = Keys.K;
            internal const Keys PcPlayerThreeRight = Keys.L;
            internal const Keys PcPlayerThreeLeft = Keys.J;
            internal const Keys PcPlayerThreeAttack = Keys.M;

            internal const Keys PcPlayerFourUp = Keys.NumPad5;
            internal const Keys PcPlayerFourDown = Keys.NumPad2;
            internal const Keys PcPlayerFourRight = Keys.NumPad3;
            internal const Keys PcPlayerFourLeft = Keys.NumPad1;
            internal const Keys PcPlayerFourAttack = Keys.NumPad0;

            internal const Keys PcPause = Keys.Enter;
        }
    }
}